package serveur;

import java.io.IOException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SealedObject;

import com.sun.org.apache.xml.internal.security.utils.Base64;
import commun.Authenticite;
import commun.Chiffrement;
import commun.InterfaceRMIConnexionSecurisee;
import commun.PaquetAuthentification;
import commun.PaquetReponseRequete;
import commun.PaquetRequete;



/**
 * 
 * Noyau du serveur.
 * 
 * @author Valentin Brémond
 * 
 * @version 2.0
 *
 */
public class KernelServeur extends UnicastRemoteObject implements InterfaceRMIConnexionSecurisee
{
	/**
	 * Utilisé par la sérialisation.
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * Permet de calculer la clé via la méthode de Diffie-Hellman.
	 */
	private long y, Y;
	
	/**
	 * La clé calculée par la méthode de Diffie-Hellman.
	 */
	private String cleDiffieHellman;
	
	/**
	 * Stocke la fonction de hachage.
	 */
	private MessageDigest hachage;
	
	/**
	 * Stocke l'algorithme de chiffrement.
	 */
	private String algorithme = "AES";
	
	/**
	 * Permet de savoir si l'étape d'authentification a été faite.
	 */
	private boolean clientAuthentifie = false;
	
	/**
	 * Stocke l'URL du RMI.
	 */
	private String urlRMI;
	

	
	// CONSTRUCTEUR
	
	
	
	/**
	 * Constructeur par défaut.
	 * 
	 * @throws UnknownHostException Si l'hôte n'existe pas.
	 * @throws RemoteException Si une erreur réseau apparaît.
	 * @throws MalformedURLException Si l'URL est mauvaise.
	 */
	public KernelServeur () throws RemoteException
	{	
		super ();
		
		// On charge la fonction de hachage
		try
		{
			hachage = MessageDigest.getInstance ("SHA-512");
		}
		catch (NoSuchAlgorithmException e)
		{
			e.printStackTrace ();
		}
		
		try
		{
			urlRMI = "rmi://" + InetAddress.getLocalHost().getHostAddress() + "/EtablissementConnexionSecurisee";
		}
		catch (UnknownHostException e)
		{
			e.printStackTrace ();
		}
		
		// On implémente les méthodes dans le registre au port 1099
		try
		{
			java.rmi.registry.LocateRegistry.createRegistry(1099);
		}
		catch (RemoteException e)
		{
			e.printStackTrace ();
		}
	    
	    // On affecte les noms
	    try
		{
			Naming.rebind(urlRMI, this);
		}
		catch (RemoteException | MalformedURLException e)
		{
			e.printStackTrace ();
		}
	}
	
	
	
	// MÉTHODES
	
	
	
	/**
	 * Main
	 */
	public static void main (String[] args)
	{
		try
		{
			new KernelServeur ();
		}
		catch (RemoteException e)
		{
			e.printStackTrace();
		}
	}

	

	@Override
	public long diffieHellman (long G, long n, long X)
	{
		// On prépare les bornes pour générer un nombre aléatoire y
		long min = 2;
		long max = n - 1;
		
		// On génère le nombre aléatoire y
		this.y = (long) (Math.random () * ((max - min) + 1)) + min;
		// Puis on en déduit Y
		this.Y = (long) (Math.pow (G, y)) % n;
		
		// On calcule la clé
		long cleDH = (long) (Math.pow (X, y)) % n;
		
		// On la rallonge puis on la tronque pour qu'elle fasse exactement 16 octets (16 chiffres)
		cleDiffieHellman = "" + cleDH + cleDH + cleDH;		
		cleDiffieHellman = cleDiffieHellman.substring (0, 16);
		
		// Et on renvoie Y pour que le client puisse calculer la clé
		return Y;
	}



	@Override
	public SealedObject verifierAuthenticite (SealedObject paquetChiffre) throws InvalidKeyException, ClassNotFoundException, IllegalBlockSizeException, BadPaddingException, IOException, NoSuchAlgorithmException, NoSuchPaddingException
	{
		// On déchiffre le paquet reçu
		Chiffrement chiffrement = new Chiffrement (cleDiffieHellman, algorithme);
		PaquetAuthentification paquetRecu = (PaquetAuthentification) chiffrement.dechiffrer (paquetChiffre);
		
		// On récupère le mot de passe associé au login
		// À terme, le mot de passe doit être récupéré dans une BDD
		String mdpHache = Base64.encode (hachage.digest ("azerty".getBytes ()));
		
		// On vérifie le paquet : s'il est bon, on renvoie un autre paquet de vérification
		if (Authenticite.verifierAuthenticite (cleDiffieHellman, mdpHache, paquetRecu))
		{	
			this.clientAuthentifie = true;
			
			// Si le paquet est bon, on renvoie un paquet de vérification
			return chiffrement.chiffrer (Authenticite.genererPaquetAuthentification (paquetRecu.getLogin (), mdpHache, cleDiffieHellman));
		}
		
		// Sinon on ne renvoie rien pour clôre l'échange
		return null;
	}



	@Override
	public SealedObject effectuerRequete (SealedObject requeteChiffree) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, ClassNotFoundException, IllegalBlockSizeException, BadPaddingException, IOException
	{
		// Si le client n'est pas authentifié, on ne retourne rien
		if (!clientAuthentifie)
		{	
			return null;
		}
		
		// On récupère la requête et on la déchiffre
		Chiffrement chiffrement = new Chiffrement (cleDiffieHellman, algorithme);
		PaquetRequete requeteRecue = (PaquetRequete) chiffrement.dechiffrer (requeteChiffree);
		
		// On effectue une requête bidon pour l'exemple
		PaquetReponseRequete paquetReponseRequete = new PaquetReponseRequete ("Réponse de la BDD.");
		
		// Puis on la renvoie, chiffrée
		return chiffrement.chiffrer (paquetReponseRequete);
	}
	
	
	
	@Override
	public String testEnvoiMessageClair (String message)
	{
		return "Message non chiffré reçu.";
	}
}
